package com.feixiang.springbootinit.utils;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.feixiang.springbootinit.common.ErrorCode;
import com.feixiang.springbootinit.exception.BusinessException;
import com.feixiang.springbootinit.exception.ThrowUtils;
import com.feixiang.springbootinit.model.entity.wechat.ChatData;
import com.feixiang.springbootinit.model.entity.wechat.MessagePullResponse;
import com.google.common.util.concurrent.RateLimiter;
import com.tencent.wework.Finance;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import sun.security.util.DerInputStream;
import sun.security.util.DerValue;
import javax.crypto.Cipher;
import java.io.File;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Arrays;
import java.util.List;
@Slf4j

/**
 * 企微会话存档工具类
 */
public class SessionArchiveUtil {
    // 会话存档SDK信息到线程中
    private static ThreadLocal<Long> sdkLocal = new ThreadLocal<>();

    private static void setSDK(Long sdk) {
        sdkLocal.set(sdk);
    }

    public static Long getSDK() {
        return sdkLocal.get();
    }

    /**
     * 初始化SDK
     *
     * @return
     */
    public static Long initSDK(String corpid, String secrectkey) {
        //判断是sdk已经存在
        ThrowUtils.throwIf(ObjectUtil.isNotNull(getSDK()), ErrorCode.OPERATION_ERROR, "sdk已经存在");
        long sdk = Finance.NewSdk();
        setSDK(sdk);
        long ret = Finance.Init(sdk, corpid, secrectkey);
        if (ret != 0) {
            destroy();
        }
        if (ObjectUtil.isNotNull(sdk)) {
            return sdk;
        }
        return sdk;
    }

    /**
     * 销毁SDK
     */
    public static void destroy() {
        if (sdkLocal.get() != null) {
            Finance.DestroySdk(sdkLocal.get());
            sdkLocal.remove();
        }
    }


    //限制频率
    private static RateLimiter limiter = RateLimiter.create(10);//每分钟不超过600次  ==> 每秒不超过10

    /**
     * 获取存档会话数据 这里是未解密的数据(特定格式)
     * 不可超过600次/分钟。
     *
     * @param sdk
     * @param seq
     * @param limit   <=1000
     * @param proxy   没有则为空
     * @param passwd  没有则为空
     * @param timeout s
     * @return
     */
//    public static List<SessionArchiveChatDataVO.ChatData> getChatData(Long sdk, Long seq, Long limit, String proxy, String passwd, Long timeout) {
//        if (timeout == null) {
//            timeout = 5L;
//        }
//        if (limit == null) {
//            limit = 20L;
//        }
//        ThrowUtils.throwIf(ObjectUtil.isNull(sdk), ErrorCode.NOT_FOUND_ERROR, "sdk不能为空");
//        ThrowUtils.throwIf(ObjectUtil.isNull(seq), ErrorCode.NOT_FOUND_ERROR, "seq不能为空");
//        //每次使用GetChatData拉取存档前需要调用NewSlice获取一个slice，在使用完slice中数据后，还需要调用FreeSlice释放。
//        long slice = Finance.NewSlice();
//        try {
//            limiter.acquire();
//            int ret = Finance.GetChatData(sdk.longValue(), seq.longValue(), limit.longValue(), proxy, passwd, timeout.longValue(), slice);
//            checkRet(ret, "init sdk err ret" + ret);
//            //获取原始未解密数据
//            String originalDataStr = Finance.GetContentFromSlice(slice);
//            SessionArchiveChatDataVO sessionArchiveOriginalDataVO = JSONUtil.toBean(originalDataStr, SessionArchiveChatDataVO.class); //解析原始数据
//            //todo 进行校验的判断
//            if (sessionArchiveOriginalDataVO.getErrcode() != 0) {
//                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取会话存档数据失败，错误码：" + sessionArchiveOriginalDataVO.getErrcode() + "，错误信息：" + sessionArchiveOriginalDataVO.getErrmsg());
//            }
//            List<SessionArchiveChatDataVO.ChatData> originalDataList = sessionArchiveOriginalDataVO.getChatdata();
//            return originalDataList;
//        } finally {
//            Finance.FreeSlice(slice);
//        }
//    }
    public static List<ChatData> getChatData(Long sdk, Long seq, Long limit, String proxy, String passwd, Long timeout) {
        if (timeout == null) {
            timeout = 5L;
        }
        if (limit == null) {
            limit = 20L;
        }
        ThrowUtils.throwIf(ObjectUtil.isNull(sdk), ErrorCode.NOT_FOUND_ERROR, "sdk不能为空");
        ThrowUtils.throwIf(ObjectUtil.isNull(seq), ErrorCode.NOT_FOUND_ERROR, "seq不能为空");
        //每次使用GetChatData拉取存档前需要调用NewSlice获取一个slice，在使用完slice中数据后，还需要调用FreeSlice释放。
        long slice = Finance.NewSlice();
        try {
            limiter.acquire();
            int ret = Finance.GetChatData(sdk.longValue(), seq.longValue(), limit.longValue(), proxy, passwd, timeout.longValue(), slice);
            checkRet(ret, "init sdk err ret" + ret);
            //获取原始未解密数据
            String originalDataStr = Finance.GetContentFromSlice(slice);
            MessagePullResponse messagePullResponse = JSONUtil.toBean(originalDataStr, MessagePullResponse.class); //解析原始数据
            //todo 进行校验的判断
            if (messagePullResponse.getErrcode() != 0) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "获取会话存档数据失败，错误码：" + messagePullResponse.getErrcode() + "，错误信息：" + messagePullResponse.getErrmsg());
            }
            List<ChatData> originalDataList = messagePullResponse.getChatdata();
            return originalDataList;
        } finally {
            Finance.FreeSlice(slice);
        }
    }

    /**
     * 专门校验Finance接口的
     *
     * @param i
     * @param msg
     */
    public static void checkRet(int i, String msg) {
        if (i != 0) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, msg);
        }
    }


    //获取到了消息，我们要对消息进行解密。还记得之前的RSA密钥对了吗，每次往企业微信里面填一次公钥，版本就加一。后面获取数据，解密的话，是需要用相应版本的私钥去解密encrypt_random_key，然后再用它用sdk去解密，得到明文消息。

    /**
     * 读取pkcs1格式的private key
     *
     * @param privKeyPEM
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String privKeyPEM) {
        String privKeyPEMnew = privKeyPEM.replaceAll("\\n", "").replace("-----BEGIN RSA PRIVATE KEY-----", "").replace("-----END RSA PRIVATE KEY-----", "");
        //byte[] bytes = java.util.Base64.getDecoder().decode(privKeyPEMnew);//Illegal base64 character d
        byte[] bytes = org.apache.commons.codec.binary.Base64.decodeBase64(privKeyPEMnew);
        try {
            DerInputStream derReader = new DerInputStream(bytes);
            DerValue[] seq = derReader.getSequence(0);
            BigInteger modulus = seq[1].getBigInteger();
            BigInteger publicExp = seq[2].getBigInteger();
            BigInteger privateExp = seq[3].getBigInteger();
            BigInteger prime1 = seq[4].getBigInteger();
            BigInteger prime2 = seq[5].getBigInteger();
            BigInteger exp1 = seq[6].getBigInteger();
            BigInteger exp2 = seq[7].getBigInteger();
            BigInteger crtCoef = seq[8].getBigInteger();
            RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
            return privateKey;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 解密encrypt_random_key
     *
     * @param privateKey
     * @param encrypt_random_key
     * @return
     */
    public static String decrptyRandomKey(PrivateKey privateKey, String encrypt_random_key) {
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 64位解码加密后的字符串
            byte[] inputArray = Base64.decodeBase64(encrypt_random_key.getBytes("UTF-8"));
            int inputLength = inputArray.length;
            // 最大解密字节数，超出最大字节数需要分组加密
            int MAX_ENCRYPT_BLOCK = 256;
            // 标识
            int offSet = 0;
            byte[] resultBytes = {};
            byte[] cache = {};
            while (inputLength - offSet > 0) {
                if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
                    offSet += MAX_ENCRYPT_BLOCK;
                } else {
                    cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
                    offSet = inputLength;
                }
                resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
                System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
            }
            return new String(resultBytes);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
    /**
     * 最后解密数据
     *
     * @param sdk
     * @param encrypt_key
     * @param encrypt_chat_msg
     * @return
     */
    public static String decryptData(long sdk, String encrypt_key, String encrypt_chat_msg) {
        //每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice，在使用完slice中数据后，还需要调用FreeSlice释放。
        long msg = Finance.NewSlice();
        try {
            int ret = Finance.DecryptData(sdk, encrypt_key, encrypt_chat_msg, msg);
            checkRet(ret, "init sdk err ret:" + ret);
            return Finance.GetContentFromSlice(msg);
        } finally {
            Finance.FreeSlice(msg);
        }
    }
    /**
     * 获取媒体文件
     *
     * @param sdkfileid
     * @param proxy
     * @param passwd
     * @param timeout
     * @param savefile  //绝对路径，一直到文件名
     */
    public static void GetMediaData(Long sdk, String sdkfileid, String proxy, String passwd, Long timeout, String savefile) {
        //拉取媒体文件
        if (timeout == null) {
            timeout = 5L;
        }
        ThrowUtils.throwIf(ObjectUtil.isNull(sdkfileid), ErrorCode.NOT_FOUND_ERROR, "sdkfileid不能为空");
        ThrowUtils.throwIf(ObjectUtil.isNull(savefile), ErrorCode.NOT_FOUND_ERROR, "savefile不能为空");
        ThrowUtils.throwIf(ObjectUtil.isNull(sdk), ErrorCode.NOT_FOUND_ERROR, "sdk不能为空");
        //媒体文件每次拉取的最大size为512k，因此超过512k的文件需要分片拉取。若该文件未拉取完整，sdk的IsMediaDataFinish接口会返回0，同时通过GetOutIndexBuf接口返回下次拉取需要传入GetMediaData的indexbuf。
        //indexbuf一般格式如右侧所示，”Range:bytes=524288-1048575“，表示这次拉取的是从524288到1048575的分片。单个文件首次拉取填写的indexbuf为空字符串，拉取后续分片时直接填入上次返回的indexbuf即可。
        String indexbuf = "";
        while (true) {
            //每次使用GetMediaData拉取存档前需要调用NewMediaData获取一个media_data，在使用完media_data中数据后，还需要调用FreeMediaData释放。
            long media_data = Finance.NewMediaData();
            try {
                int ret = Finance.GetMediaData(sdk.longValue(), indexbuf, sdkfileid, proxy, passwd, timeout.longValue(), media_data);
                checkRet(ret, "getmediadata ret:" + ret);
                log.info("getmediadata outindex len:{}, data_len:{}, is_finis:{}\n", Finance.GetIndexLen(media_data), Finance.GetDataLen(media_data), Finance.IsMediaDataFinish(media_data));
                //大于512k的文件会分片拉取，此处需要使用追加写，避免后面的分片覆盖之前的数据。
                FileOutputStream outputStream = new FileOutputStream(new File(savefile), true);
                outputStream.write(Finance.GetData(media_data));
                outputStream.close();
                if (Finance.IsMediaDataFinish(media_data) == 1) {
                    //已经拉取完成最后一个分片
                    break;
                } else {
                    //获取下次拉取需要使用的indexbuf
                    indexbuf = Finance.GetOutIndexBuf(media_data);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                Finance.FreeMediaData(media_data);
            }
        }
    }

}
